package com.hqd.ch03.v50.boot.context.properties;

import com.hqd.ch03.v50.aware.InitializingBean;
import com.hqd.ch03.v50.beans.BeansException;
import com.hqd.ch03.v50.beans.factory.config.BeanDefinition;
import com.hqd.ch03.v50.beans.factory.support.BeanDefinitionBuilder;
import com.hqd.ch03.v50.config.BeanPostProcessor;
import com.hqd.ch03.v50.context.ApplicationContext;
import com.hqd.ch03.v50.context.ApplicationContextAware;
import com.hqd.ch03.v50.core.Ordered;
import com.hqd.ch03.v50.core.PriorityOrdered;
import com.hqd.ch03.v50.registry.BeanDefinitionRegistry;

public class ConfigurationPropertiesBindingPostProcessor
        implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {

    /**
     * The bean name that this post-processor is registered with.
     */
    public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName();

    private ApplicationContext applicationContext;

    private BeanDefinitionRegistry registry;

    private ConfigurationPropertiesBinder binder;

    /**
     * Register a {@link ConfigurationPropertiesBindingPostProcessor} bean if one is not
     * already registered.
     *
     * @param registry the bean definition registry
     * @since 2.2.0
     */
    public static void register(BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition(BEAN_NAME)) {
            BeanDefinition definition = BeanDefinitionBuilder
                    .rootBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class).getBeanDefinition();
            definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN_NAME, definition);
        }
        ConfigurationPropertiesBinder.register(registry);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // We can't use constructor injection of the application context because
        // it causes eager factory bean initialization
        this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
        this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
        return bean;
    }

    private void bind(ConfigurationPropertiesBean bean) {
        if (bean == null || hasBoundValueObject(bean.getName())) {
            return;
        }
        try {
            this.binder.bind(bean);
        } catch (Exception ex) {
            throw new ConfigurationPropertiesBindException(bean, ex);
        }
    }

    private boolean hasBoundValueObject(String beanName) {
        return this.registry.containsBeanDefinition(beanName) && ConfigurationPropertiesBean.BindMethod.VALUE_OBJECT
                .equals(this.registry.getBeanDefinition(beanName).getAttribute(ConfigurationPropertiesBean.BindMethod.class.getName()));
    }

}
